package gl.java.game;

import com.sun.istack.internal.Nullable;
import lombok.extern.slf4j.Slf4j;

import java.util.List;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 定时器
 */
@Slf4j
public class Timer {
    private AtomicInteger GID = new AtomicInteger(0);
    private ScheduledThreadPoolExecutor exec = new ScheduledThreadPoolExecutor(1, new ThreadFactory() {
        @Override
        public Thread newThread(Runnable r) {
            Thread thread = new Thread(r);
            thread.setName("Timer");
            log.debug("Create A Timer Thread.name:{}",thread.getName());
            return thread;
        }
    });
    private List<Task> timerList = new java.util.concurrent.CopyOnWriteArrayList<Task>();

    public class Task {

        private final Runnable runnable;
        private final long millisecond;
        /**
         * false是forever,true是disposable
         */
        public boolean disposable;
        public long ID;
        public Runnable proxy;
        public ScheduledFuture<?> future;


        public Task(Runnable runnable, long millisecond) {
            this.runnable = runnable;
            this.millisecond = millisecond;
            ID = GID.getAndIncrement();
        }

        /**
         * @param runnable    function
         * @param millisecond interval
         * @param disposable  @see ScriptTimer#disposeCount
         */
        public Task(Runnable runnable, long millisecond, boolean disposable) {
            this(runnable, millisecond);
            this.disposable = disposable;
        }
    }

    public interface ThreadBinder {
        void onDeliver(Runnable runnable);
    }

    public long setTimeout(@Nullable Runnable runnable, long millisecond) {
        return setTimeout(runnable, millisecond, null);
    }

    public long setTimeout(@Nullable Runnable runnable, long millisecond, ThreadBinder threadBinder) {
        Task t = createTask(runnable, millisecond, threadBinder, true);
        t.future = exec.schedule(t.proxy, millisecond, TimeUnit.MILLISECONDS);
        return t.ID;
    }

    public long setInterval(@Nullable Runnable runnable, long millisecond) {
        return setInterval(runnable, millisecond, null);
    }

    public long setInterval(@Nullable Runnable runnable, long millisecond, ThreadBinder threadBinder) {
        Task t = createTask(runnable, millisecond, threadBinder, false);
        t.future = exec.scheduleAtFixedRate(t.proxy, millisecond, millisecond, TimeUnit.MILLISECONDS);
        return t.ID;
    }

    private Task createTask(@Nullable Runnable runnable, long millisecond, ThreadBinder threadBinder, boolean disposable) {
        Task t = new Task(runnable, millisecond, disposable);
        t.proxy = () -> {
            if (threadBinder != null) {
                threadBinder.onDeliver(runnable);
            } else {
                runnable.run();
            }
            log.debug("run task :{} task:{}", t.ID, t.proxy);
        };
        log.debug("create Task  :{} task:{}", t.ID, t.proxy);
        timerList.add(t);
        return t;
    }

    private void removeTimer(@Nullable long id) {
        log.debug("removeTimer :{}", id);
        for (Task timer : timerList) {
            if (id == timer.ID) {
                timerList.remove(timer);
                timer.future.cancel(true);
//                log.info("removeTimer ok :{} task:{}", timer.ID ,timer.proxy);
                break;
            }
        }
    }

    public void clearInterval(@Nullable long id) {
        removeTimer(id);
    }

    public void clearTimeout(@Nullable long id) {
        removeTimer(id);
    }

    public void clearAll() {
        exec.shutdown();
        timerList.clear();
    }

}
